<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Go语言中的并发编程 | 小狐档案库</title>
    <meta name="generator" content="VuePress 1.8.2">
    <link rel="icon" href="/favicon.ico">
    <meta name="description" content="学习资料">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    
    <link rel="preload" href="/assets/css/0.styles.0c36f186.css" as="style"><link rel="preload" href="/assets/js/app.7029ddab.js" as="script"><link rel="preload" href="/assets/js/3.ebaa3085.js" as="script"><link rel="preload" href="/assets/js/1.8ce67e8c.js" as="script"><link rel="preload" href="/assets/js/15.74a9f029.js" as="script"><link rel="prefetch" href="/assets/js/10.15c1f9c8.js"><link rel="prefetch" href="/assets/js/11.dc2fbddf.js"><link rel="prefetch" href="/assets/js/12.26ab1f36.js"><link rel="prefetch" href="/assets/js/13.6e1d4dbc.js"><link rel="prefetch" href="/assets/js/14.0cd48e80.js"><link rel="prefetch" href="/assets/js/16.24ef3382.js"><link rel="prefetch" href="/assets/js/17.c8a9bd0f.js"><link rel="prefetch" href="/assets/js/18.fbd494c2.js"><link rel="prefetch" href="/assets/js/19.f9a0df71.js"><link rel="prefetch" href="/assets/js/20.e6d0df38.js"><link rel="prefetch" href="/assets/js/21.932645cc.js"><link rel="prefetch" href="/assets/js/22.d6027dce.js"><link rel="prefetch" href="/assets/js/23.53bce141.js"><link rel="prefetch" href="/assets/js/24.ed3b8b64.js"><link rel="prefetch" href="/assets/js/25.ed88877e.js"><link rel="prefetch" href="/assets/js/26.f5ad24d5.js"><link rel="prefetch" href="/assets/js/27.64ad45b4.js"><link rel="prefetch" href="/assets/js/28.f308f8ab.js"><link rel="prefetch" href="/assets/js/29.4d95742d.js"><link rel="prefetch" href="/assets/js/30.0a867de0.js"><link rel="prefetch" href="/assets/js/31.f12def86.js"><link rel="prefetch" href="/assets/js/32.6b4fd8b7.js"><link rel="prefetch" href="/assets/js/33.4bb2aa9c.js"><link rel="prefetch" href="/assets/js/34.013e731c.js"><link rel="prefetch" href="/assets/js/35.6b263ef8.js"><link rel="prefetch" href="/assets/js/36.06acf3cc.js"><link rel="prefetch" href="/assets/js/37.fe72c29c.js"><link rel="prefetch" href="/assets/js/38.9b4e0bd6.js"><link rel="prefetch" href="/assets/js/39.0cde0408.js"><link rel="prefetch" href="/assets/js/4.9b5650bb.js"><link rel="prefetch" href="/assets/js/40.56bda9a8.js"><link rel="prefetch" href="/assets/js/41.007ae34c.js"><link rel="prefetch" href="/assets/js/42.bdaf414b.js"><link rel="prefetch" href="/assets/js/43.78d32d4e.js"><link rel="prefetch" href="/assets/js/44.a10cf8a5.js"><link rel="prefetch" href="/assets/js/45.19e14721.js"><link rel="prefetch" href="/assets/js/46.57ebcf06.js"><link rel="prefetch" href="/assets/js/47.9a513ffe.js"><link rel="prefetch" href="/assets/js/48.245b165a.js"><link rel="prefetch" href="/assets/js/49.fab43655.js"><link rel="prefetch" href="/assets/js/5.a16ef7f8.js"><link rel="prefetch" href="/assets/js/50.48c191fd.js"><link rel="prefetch" href="/assets/js/51.28a94b0e.js"><link rel="prefetch" href="/assets/js/52.e783fbf9.js"><link rel="prefetch" href="/assets/js/53.2f0e960e.js"><link rel="prefetch" href="/assets/js/54.577ff54e.js"><link rel="prefetch" href="/assets/js/55.4bb99598.js"><link rel="prefetch" href="/assets/js/56.e820e954.js"><link rel="prefetch" href="/assets/js/57.0cea0cfa.js"><link rel="prefetch" href="/assets/js/58.e31cfcd0.js"><link rel="prefetch" href="/assets/js/59.458ab168.js"><link rel="prefetch" href="/assets/js/6.7da424ba.js"><link rel="prefetch" href="/assets/js/60.e9caace2.js"><link rel="prefetch" href="/assets/js/61.0b61afdb.js"><link rel="prefetch" href="/assets/js/62.c240acab.js"><link rel="prefetch" href="/assets/js/63.71cd2a97.js"><link rel="prefetch" href="/assets/js/64.977ea29b.js"><link rel="prefetch" href="/assets/js/65.79b2d62a.js"><link rel="prefetch" href="/assets/js/66.8da69071.js"><link rel="prefetch" href="/assets/js/67.fdc57d7e.js"><link rel="prefetch" href="/assets/js/68.d43eabe9.js"><link rel="prefetch" href="/assets/js/7.6e92edd1.js"><link rel="prefetch" href="/assets/js/8.2389170b.js"><link rel="prefetch" href="/assets/js/9.77841f24.js">
    <link rel="stylesheet" href="/assets/css/0.styles.0c36f186.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container" data-v-1156296a><div data-v-1156296a><div id="loader-wrapper" class="loading-wrapper" data-v-d48f4d20 data-v-1156296a data-v-1156296a><div class="loader-main" data-v-d48f4d20><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div><div data-v-d48f4d20></div></div> <!----> <!----></div> <div class="password-shadow password-wrapper-out" style="display:none;" data-v-4e82dffc data-v-1156296a data-v-1156296a><h3 class="title" data-v-4e82dffc data-v-4e82dffc>小狐档案库</h3> <p class="description" data-v-4e82dffc data-v-4e82dffc>学习资料</p> <label id="box" class="inputBox" data-v-4e82dffc data-v-4e82dffc><input type="password" value="" data-v-4e82dffc> <span data-v-4e82dffc>Konck! Knock!</span> <button data-v-4e82dffc>OK</button></label> <div class="footer" data-v-4e82dffc data-v-4e82dffc><span data-v-4e82dffc><i class="iconfont reco-theme" data-v-4e82dffc></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-4e82dffc>vuePress-theme-reco</a></span> <span data-v-4e82dffc><i class="iconfont reco-copyright" data-v-4e82dffc></i> <a data-v-4e82dffc><span data-v-4e82dffc>小狐</span>
            
          <span data-v-4e82dffc>2021-9-1 - </span>
          2021
        </a></span></div></div> <div class="hide" data-v-1156296a><header class="navbar" data-v-1156296a><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="/logo.png" alt="小狐档案库" class="logo"> <span class="site-name">小狐档案库</span></a> <div class="links"><div class="color-picker"><a class="color-button"><i class="iconfont reco-color"></i></a> <div class="color-picker-menu" style="display:none;"><div class="mode-options"><h4 class="title">Choose mode</h4> <ul class="color-mode-options"><li class="dark">dark</li><li class="auto active">auto</li><li class="light">light</li></ul></div></div></div> <div class="search-box"><i class="iconfont reco-search"></i> <input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/GO/" class="nav-link"><i class="undefined"></i>
  GO
</a></li><li class="dropdown-item"><!----> <a href="/categories/面试系列/" class="nav-link"><i class="undefined"></i>
  面试系列
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  Tag
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  足迹
</a></div><div class="nav-item"><a href="https://www.xiaohu.team" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-message"></i>
  摇光图库
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></div> <!----></nav></div></header> <div class="sidebar-mask" data-v-1156296a></div> <aside class="sidebar" data-v-1156296a><div class="personal-info-wrapper" data-v-828910c6 data-v-1156296a><img src="/avatar.jpg" alt="author-avatar" class="personal-img" data-v-828910c6> <h3 class="name" data-v-828910c6>
    小狐
  </h3> <div class="num" data-v-828910c6><div data-v-828910c6><h3 data-v-828910c6>58</h3> <h6 data-v-828910c6>Articles</h6></div> <div data-v-828910c6><h3 data-v-828910c6>20</h3> <h6 data-v-828910c6>Tags</h6></div></div> <ul class="social-links" data-v-828910c6></ul> <hr data-v-828910c6></div> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link"><i class="iconfont reco-home"></i>
  首页
</a></div><div class="nav-item"><div class="dropdown-wrapper"><a class="dropdown-title"><span class="title"><i class="iconfont reco-category"></i>
      分类
    </span> <span class="arrow right"></span></a> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/categories/GO/" class="nav-link"><i class="undefined"></i>
  GO
</a></li><li class="dropdown-item"><!----> <a href="/categories/面试系列/" class="nav-link"><i class="undefined"></i>
  面试系列
</a></li></ul></div></div><div class="nav-item"><a href="/tag/" class="nav-link"><i class="iconfont reco-tag"></i>
  Tag
</a></div><div class="nav-item"><a href="/timeline/" class="nav-link"><i class="iconfont reco-date"></i>
  足迹
</a></div><div class="nav-item"><a href="https://www.xiaohu.team" target="_blank" rel="noopener noreferrer" class="nav-link external"><i class="iconfont reco-message"></i>
  摇光图库
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></div> <!----></nav> <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading open"><span>go语言学习</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading open"><span>go语言基础学习</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/blogs/GO/go/1_go_env.html" class="sidebar-link">Go环境安装配置</a></li><li><a href="/blogs/GO/go/2_go_datatype.html" class="sidebar-link">GO基础-基本数据类型</a></li><li><a href="/blogs/GO/go/3_go_var_const.html" class="sidebar-link">Go基础-变量和常量</a></li><li><a href="/blogs/GO/go/5_go_operation.html" class="sidebar-link">GO语言基础之运算符</a></li><li><a href="/blogs/GO/go/24_go_fmt.html" class="sidebar-link">Go语言fmt.Printf使用指南</a></li><li><a href="/blogs/GO/go/25_go_flag.html" class="sidebar-link">Go语言fmt.Printf使用指南</a></li><li><a href="/blogs/GO/go/6_go_array.html" class="sidebar-link">Go语言基础之数组</a></li><li><a href="/blogs/GO/go/7_go_slice.html" class="sidebar-link">Go语言基础之切片</a></li><li><a href="/blogs/GO/go/8_go_map.html" class="sidebar-link">Go语言基础之map</a></li><li><a href="/blogs/GO/go/9_go_pointer.html" class="sidebar-link">Go语言基础之指针</a></li><li><a href="/blogs/GO/go/10_go_func.html" class="sidebar-link">Go语言基础之函数</a></li><li><a href="/blogs/GO/go/11_go_struct.html" class="sidebar-link">Go语言基础之结构体</a></li><li><a href="/blogs/GO/go/12_go_interface.html" class="sidebar-link">Go语言基础之接口</a></li><li><a href="/blogs/GO/go/26_go_reflect.html" class="sidebar-link">Go语言基础之反射</a></li><li><a href="/blogs/GO/go/13_go_package.html" class="sidebar-link">Go语言基础之包(package)</a></li><li><a href="/blogs/GO/go/14_go_channel_lock.html" aria-current="page" class="active sidebar-link">Go语言中的并发编程</a></li><li><a href="/blogs/GO/go/15_go_socket_udp_tcp.html" class="sidebar-link">Go语言基础之网络编程</a></li><li><a href="/blogs/GO/go/16_go_net_http.html" class="sidebar-link">Go语言基础之net/http介绍</a></li><li><a href="/blogs/GO/go/23_go_Context.html" class="sidebar-link">Go标准库Context</a></li><li><a href="/blogs/GO/go/17_go_test.html" class="sidebar-link">Go语言基础之单元测试</a></li><li><a href="/blogs/GO/go/18_go_mod.html" class="sidebar-link">Go语言基础之依赖管理</a></li><li><a href="/blogs/GO/go/19_go_time.html" class="sidebar-link">Go语言基础之time包</a></li><li><a href="/blogs/GO/go/20_go_strconv.html" class="sidebar-link">Go语言内置包之strconv</a></li><li><a href="/blogs/GO/go/22_go_http_template_1.html" class="sidebar-link">Go语言标准库之http/template</a></li></ul></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>gin</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>数据库操作</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>gorm</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>gorm</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>NoSql</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>消息队列</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable is-sub-group depth-1"><p class="sidebar-heading"><span>go三方库</span> <span class="arrow right"></span></p> <!----></section></li></ul></section></li></ul> </aside> <div class="password-shadow password-wrapper-in" style="display:none;" data-v-4e82dffc data-v-1156296a><h3 class="title" data-v-4e82dffc data-v-4e82dffc>Go语言中的并发编程</h3> <!----> <label id="box" class="inputBox" data-v-4e82dffc data-v-4e82dffc><input type="password" value="" data-v-4e82dffc> <span data-v-4e82dffc>Konck! Knock!</span> <button data-v-4e82dffc>OK</button></label> <div class="footer" data-v-4e82dffc data-v-4e82dffc><span data-v-4e82dffc><i class="iconfont reco-theme" data-v-4e82dffc></i> <a target="blank" href="https://vuepress-theme-reco.recoluan.com" data-v-4e82dffc>vuePress-theme-reco</a></span> <span data-v-4e82dffc><i class="iconfont reco-copyright" data-v-4e82dffc></i> <a data-v-4e82dffc><span data-v-4e82dffc>小狐</span>
            
          <span data-v-4e82dffc>2021-9-1 - </span>
          2021
        </a></span></div></div> <div data-v-1156296a><main class="page"><section><div class="page-title"><h1 class="title">Go语言中的并发编程</h1> <div data-v-1ff7123e><i class="iconfont reco-account" data-v-1ff7123e><span data-v-1ff7123e>小狐</span></i> <i class="iconfont reco-date" data-v-1ff7123e><span data-v-1ff7123e>9/7/2021</span></i> <!----> <i class="tags iconfont reco-tag" data-v-1ff7123e><span class="tag-item" data-v-1ff7123e>GO</span></i></div></div> <div class="theme-reco-content content__default"><h1 id="go语言基础之并发"><a href="#go语言基础之并发" class="header-anchor">#</a> Go语言基础之并发</h1> <p>并发是编程里面一个非常重要的概念，Go语言在语言层面天生支持并发，这也是Go语言流行的一个很重要的原因。</p> <h1 id="go语言中的并发编程"><a href="#go语言中的并发编程" class="header-anchor">#</a> Go语言中的并发编程</h1> <h2 id="并发与并行"><a href="#并发与并行" class="header-anchor">#</a> 并发与并行</h2> <p>并发：同一时间段内执行多个任务（你在用微信和两个女朋友聊天）。</p> <p>并行：同一时刻执行多个任务（你和你朋友都在用微信和女朋友聊天）。</p> <p>Go语言的并发通过<code>goroutine</code>实现。<code>goroutine</code>类似于线程，属于用户态的线程，我们可以根据需要创建成千上万个<code>goroutine</code>并发工作。<code>goroutine</code>是由Go语言的运行时（runtime）调度完成，而线程是由操作系统调度完成。</p> <p>Go语言还提供<code>channel</code>在多个<code>goroutine</code>间进行通信。<code>goroutine</code>和<code>channel</code>是 Go 语言秉承的 CSP（Communicating Sequential Process）并发模式的重要实现基础。</p> <h2 id="goroutine"><a href="#goroutine" class="header-anchor">#</a> goroutine</h2> <p>在java/c++中我们要实现并发编程的时候，我们通常需要自己维护一个线程池，并且需要自己去包装一个又一个的任务，同时需要自己去调度线程执行任务并维护上下文切换，这一切通常会耗费程序员大量的心智。那么能不能有一种机制，程序员只需要定义很多个任务，让系统去帮助我们把这些任务分配到CPU上实现并发执行呢？</p> <p>Go语言中的<code>goroutine</code>就是这样一种机制，<code>goroutine</code>的概念类似于线程，但 <code>goroutine</code>是由Go的运行时（runtime）调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言，就是因为它在语言层面已经内置了调度和上下文切换的机制。</p> <p>在Go语言编程中你不需要去自己写进程、线程、协程，你的技能包里只有一个技能–<code>goroutine</code>，当你需要让某个任务并发执行的时候，你只需要把这个任务包装成一个函数，开启一个<code>goroutine</code>去执行这个函数就可以了，就是这么简单粗暴。</p> <h3 id="使用goroutine"><a href="#使用goroutine" class="header-anchor">#</a> 使用goroutine</h3> <p>Go语言中使用<code>goroutine</code>非常简单，只需要在调用函数的时候在前面加上<code>go</code>关键字，就可以为一个函数创建一个<code>goroutine</code>。</p> <p>一个<code>goroutine</code>必定对应一个函数，可以创建多个<code>goroutine</code>去执行相同的函数。</p> <h3 id="启动单个goroutine"><a href="#启动单个goroutine" class="header-anchor">#</a> 启动单个goroutine</h3> <p>启动goroutine的方式非常简单，只需要在调用的函数（普通函数和匿名函数）前面加上一个<code>go</code>关键字。</p> <p>举个例子如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;Hello Goroutine!&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;main goroutine done!&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>这个示例中hello函数和下面的语句是串行的，执行的结果是打印完<code>Hello Goroutine!</code>后打印<code>main goroutine done!</code>。</p> <p>接下来我们在调用hello函数前面加上关键字<code>go</code>，也就是启动一个goroutine去执行hello这个函数。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">go</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 启动另外一个goroutine去执行hello函数</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;main goroutine done!&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>这一次的执行结果只打印了<code>main goroutine done!</code>，并没有打印<code>Hello Goroutine!</code>。为什么呢？</p> <p>在程序启动时，Go程序就会为<code>main()</code>函数创建一个默认的<code>goroutine</code>。</p> <p>当main()函数返回的时候该<code>goroutine</code>就结束了，所有在<code>main()</code>函数中启动的<code>goroutine</code>会一同结束，<code>main</code>函数所在的<code>goroutine</code>就像是权利的游戏中的夜王，其他的<code>goroutine</code>都是异鬼，夜王一死它转化的那些异鬼也就全部GG了。</p> <p>所以我们要想办法让main函数等一等hello函数，最简单粗暴的方式就是<code>time.Sleep</code>了。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">go</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 启动另外一个goroutine去执行hello函数</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;main goroutine done!&quot;</span><span class="token punctuation">)</span>
	time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>执行上面的代码你会发现，这一次先打印<code>main goroutine done!</code>，然后紧接着打印<code>Hello Goroutine!</code>。</p> <p>首先为什么会先打印<code>main goroutine done!</code>是因为我们在创建新的goroutine的时候需要花费一些时间，而此时main函数所在的<code>goroutine</code>是继续执行的。</p> <h3 id="启动多个goroutine"><a href="#启动多个goroutine" class="header-anchor">#</a> 启动多个goroutine</h3> <p>在Go语言中实现并发就是这样简单，我们还可以启动多个<code>goroutine</code>。让我们再来一个例子： （这里使用了<code>sync.WaitGroup</code>来实现goroutine的同步）</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>i <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">defer</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// goroutine结束就登记-1</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;Hello Goroutine!&quot;</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// 启动一个goroutine就登记+1</span>
		<span class="token keyword">go</span> <span class="token function">hello</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 等待所有登记的goroutine都结束</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>多次执行上面的代码，会发现每次打印的数字的顺序都不一致。这是因为10个<code>goroutine</code>是并发执行的，而<code>goroutine</code>的调度是随机的。</p> <h2 id="goroutine与线程"><a href="#goroutine与线程" class="header-anchor">#</a> goroutine与线程</h2> <h3 id="可增长的栈"><a href="#可增长的栈" class="header-anchor">#</a> 可增长的栈</h3> <p>OS线程（操作系统线程）一般都有固定的栈内存（通常为2MB）,一个<code>goroutine</code>的栈在其生命周期开始时只有很小的栈（典型情况下2KB），<code>goroutine</code>的栈不是固定的，他可以按需增大和缩小，<code>goroutine</code>的栈大小限制可以达到1GB，虽然极少会用到这么大。所以在Go语言中一次创建十万左右的<code>goroutine</code>也是可以的。</p> <h3 id="goroutine调度"><a href="#goroutine调度" class="header-anchor">#</a> goroutine调度</h3> <p><code>GPM</code>是Go语言运行时（runtime）层面的实现，是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。</p> <ul><li><code>G</code>很好理解，就是个goroutine的，里面除了存放本goroutine信息外 还有与所在P的绑定等信息。</li> <li><code>P</code>管理着一组goroutine队列，P里面会存储当前goroutine运行的上下文环境（函数指针，堆栈地址及地址边界），P会对自己管理的goroutine队列做一些调度（比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等）当自己的队列消费完了就去全局队列里取，如果全局队列里也消费完了会去其他P的队列里抢任务。</li> <li><code>M（machine）</code>是Go运行时（runtime）对操作系统内核线程的虚拟， M与内核线程一般是一一映射的关系， 一个groutine最终是要放到M上执行的；</li></ul> <p>P与M一般也是一一对应的。他们关系是： P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时，runtime会新建一个M，阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。</p> <p>P的个数是通过<code>runtime.GOMAXPROCS</code>设定（最大256），Go1.5版本之后默认为物理线程数。 在并发量大的时候会增加一些P和M，但不会太多，切换太频繁的话得不偿失。</p> <p>单从线程调度讲，Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的，<code>goroutine</code>则是由Go运行时（runtime）自己的调度器调度的，这个调度器使用一个称为m:n调度的技术（复用/调度m个goroutine到n个OS线程）。 其一大特点是goroutine的调度是在用户态下完成的， 不涉及内核态与用户态之间的频繁切换，包括内存的分配与释放，都是在用户态维护着一块大的内存池， 不直接调用系统的malloc函数（除非内存池需要改变），成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源，近似的把若干goroutine均分在物理线程上， 再加上本身goroutine的超轻量，以上种种保证了go调度方面的性能。</p> <p><a href="https://www.cnblogs.com/sunsky303/p/9705727.html" target="_blank" rel="noopener noreferrer">点我了解更多<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></p> <h3 id="gomaxprocs"><a href="#gomaxprocs" class="header-anchor">#</a> GOMAXPROCS</h3> <p>Go运行时的调度器使用<code>GOMAXPROCS</code>参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上，调度器会把Go代码同时调度到8个OS线程上（GOMAXPROCS是m:n调度中的n）。</p> <p>Go语言中可以通过<code>runtime.GOMAXPROCS()</code>函数设置当前程序并发时占用的CPU逻辑核心数。</p> <p>Go1.5版本之前，默认使用的是单核心执行。Go1.5版本之后，默认使用全部的CPU逻辑核心数。</p> <p>我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果，这里举个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;A:&quot;</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;B:&quot;</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	runtime<span class="token punctuation">.</span><span class="token function">GOMAXPROCS</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>两个任务只有一个逻辑核心，此时是做完一个任务再做另一个任务。 将逻辑核心数设为2，此时两个任务并行执行，代码如下。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;A:&quot;</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;B:&quot;</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	runtime<span class="token punctuation">.</span><span class="token function">GOMAXPROCS</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>Go语言中的操作系统线程和goroutine的关系：</p> <ol><li>一个操作系统线程对应用户态多个goroutine。</li> <li>go程序可以同时使用多个操作系统线程。</li> <li>goroutine和OS线程是多对多的关系，即m:n。</li></ol> <h2 id="channel"><a href="#channel" class="header-anchor">#</a> channel</h2> <p>单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。</p> <p>虽然可以使用共享内存进行数据交换，但是共享内存在不同的<code>goroutine</code>中容易发生竞态问题。为了保证数据交换的正确性，必须使用互斥量对内存进行加锁，这种做法势必造成性能问题。</p> <p>Go语言的并发模型是<code>CSP（Communicating Sequential Processes）</code>，提倡<strong>通过通信共享内存</strong>而不是<strong>通过共享内存而实现通信</strong>。</p> <p>如果说<code>goroutine</code>是Go程序并发的执行体，<code>channel</code>就是它们之间的连接。<code>channel</code>是可以让一个<code>goroutine</code>发送特定值到另一个<code>goroutine</code>的通信机制。</p> <p>Go 语言中的通道（channel）是一种特殊的类型。通道像一个传送带或者队列，总是遵循先入先出（First In First Out）的规则，保证收发数据的顺序。每一个通道都是一个具体类型的导管，也就是声明channel的时候需要为其指定元素类型。</p> <h3 id="channel类型"><a href="#channel类型" class="header-anchor">#</a> channel类型</h3> <p><code>channel</code>是一种类型，一种引用类型。声明通道类型的格式如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> 变量 <span class="token keyword">chan</span> 元素类型
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>举几个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> ch1 <span class="token keyword">chan</span> <span class="token builtin">int</span>   <span class="token comment">// 声明一个传递整型的通道</span>
<span class="token keyword">var</span> ch2 <span class="token keyword">chan</span> <span class="token builtin">bool</span>  <span class="token comment">// 声明一个传递布尔型的通道</span>
<span class="token keyword">var</span> ch3 <span class="token keyword">chan</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span> <span class="token comment">// 声明一个传递int切片的通道</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><h3 id="创建channel"><a href="#创建channel" class="header-anchor">#</a> 创建channel</h3> <p>通道是引用类型，通道类型的空值是<code>nil</code>。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> ch <span class="token keyword">chan</span> intfmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span> <span class="token comment">// &lt;nil&gt;</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>声明的通道后需要使用<code>make</code>函数初始化之后才能使用。</p> <p>创建channel的格式如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> 元素类型<span class="token punctuation">,</span> <span class="token punctuation">[</span>缓冲大小<span class="token punctuation">]</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>channel的缓冲大小是可选的。</p> <p>举几个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code>ch4 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>ch5 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">bool</span><span class="token punctuation">)</span>ch6 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h3 id="channel操作"><a href="#channel操作" class="header-anchor">#</a> channel操作</h3> <p>通道有发送（send）、接收(receive）和关闭（close）三种操作。</p> <p>发送和接收都使用<code>&lt;-</code>符号。</p> <p>现在我们先使用以下语句定义一个通道：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code>ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h4 id="发送"><a href="#发送" class="header-anchor">#</a> 发送</h4> <p>将一个值发送到通道中。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code>ch <span class="token operator">&lt;-</span> <span class="token number">10</span> <span class="token comment">// 把10发送到ch中</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h4 id="接收"><a href="#接收" class="header-anchor">#</a> 接收</h4> <p>从一个通道中接收值。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code>x <span class="token operator">:=</span> <span class="token operator">&lt;-</span> ch <span class="token comment">// 从ch中接收值并赋值给变量x&lt;-ch       // 从ch中接收值，忽略结果</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h4 id="关闭"><a href="#关闭" class="header-anchor">#</a> 关闭</h4> <p>我们通过调用内置的<code>close</code>函数来关闭通道。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token function">close</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>关于关闭通道需要注意的事情是，只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的，它和关闭文件是不一样的，在结束操作之后关闭文件是必须要做的，但关闭通道不是必须的。</p> <p>关闭后的通道有以下特点：</p> <ol><li>对一个关闭的通道再发送值就会导致panic。</li> <li>对一个关闭的通道进行接收会一直获取值直到通道为空。</li> <li>对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。</li> <li>关闭一个已经关闭的通道会导致panic。</li></ol> <h3 id="无缓冲的通道"><a href="#无缓冲的通道" class="header-anchor">#</a> 无缓冲的通道</h3> <p>无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>
	ch <span class="token operator">&lt;-</span> <span class="token number">10</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;发送成功&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面这段代码能够通过编译，但是执行的时候会出现以下错误：</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>fatal error: all goroutines are asleep - deadlock<span class="token operator">!</span>

goroutine <span class="token number">1</span> <span class="token punctuation">[</span>chan send<span class="token punctuation">]</span>:
main.main<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">..</span>./src/github.com/Q1mi/studygo/day06/channel02/main.go:8 +0x54
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>为什么会出现<code>deadlock</code>错误呢？</p> <p>因为我们使用<code>ch := make(chan int)</code>创建的是无缓冲的通道，无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点，快递员给你打电话必须要把这个物品送到你的手中，简单来说就是无缓冲的通道必须有接收才能发送。</p> <p>上面的代码会阻塞在<code>ch &lt;- 10</code>这一行代码形成死锁，那如何解决这个问题呢？</p> <p>一种方法是启用一个<code>goroutine</code>去接收值，例如：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">recv</span><span class="token punctuation">(</span>c <span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	ret <span class="token operator">:=</span> <span class="token operator">&lt;-</span>c
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;接收成功&quot;</span><span class="token punctuation">,</span> ret<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">recv</span><span class="token punctuation">(</span>ch<span class="token punctuation">)</span> <span class="token comment">// 启用goroutine从通道接收值</span>
	ch <span class="token operator">&lt;-</span> <span class="token number">10</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;发送成功&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>无缓冲通道上的发送操作会阻塞，直到另一个<code>goroutine</code>在该通道上执行接收操作，这时值才能发送成功，两个<code>goroutine</code>将继续执行。相反，如果接收操作先执行，接收方的goroutine将阻塞，直到另一个<code>goroutine</code>在该通道上发送一个值。</p> <p>使用无缓冲通道进行通信将导致发送和接收的<code>goroutine</code>同步化。因此，无缓冲通道也被称为<code>同步通道</code>。</p> <h3 id="有缓冲的通道"><a href="#有缓冲的通道" class="header-anchor">#</a> 有缓冲的通道</h3> <p>解决上面问题的方法还有一种就是使用有缓冲区的通道。我们可以在使用make函数初始化通道的时候为其指定通道的容量，例如：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>	ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// 创建一个容量为1的有缓冲区通道	ch &lt;- 10	fmt.Println(&quot;发送成功&quot;)}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>只要通道的容量大于零，那么该通道就是有缓冲的通道，通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子，格子满了就装不下了，就阻塞了，等到别人取走一个快递员就能往里面放一个。</p> <p>我们可以使用内置的<code>len</code>函数获取通道内元素的数量，使用<code>cap</code>函数获取通道的容量，虽然我们很少会这么做。</p> <h3 id="for-range从通道循环取值"><a href="#for-range从通道循环取值" class="header-anchor">#</a> for range从通道循环取值</h3> <p>当向通道中发送完数据时，我们可以通过<code>close</code>函数来关闭通道。</p> <p>当通道被关闭时，再往该通道发送值会引发<code>panic</code>，从该通道取值的操作会先取完通道中的值，再然后取到的值一直都是对应类型的零值。那如何判断一个通道是否被关闭了呢？</p> <p>我们来看下面这个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token comment">// channel 练习func main() {	ch1 := make(chan int)	ch2 := make(chan int)	// 开启goroutine将0~100的数发送到ch1中	go func() {		for i := 0; i &lt; 100; i++ {			ch1 &lt;- i		}		close(ch1)	}()	// 开启goroutine从ch1中接收值，并将该值的平方发送到ch2中	go func() {		for {			i, ok := &lt;-ch1 // 通道关闭后再取值ok=false			if !ok {				break			}			ch2 &lt;- i * i		}		close(ch2)	}()	// 在主goroutine中从ch2中接收值打印	for i := range ch2 { // 通道关闭后会退出for range循环		fmt.Println(i)	}}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>从上面的例子中我们看到有两种方式在接收值的时候判断该通道是否被关闭，不过我们通常使用的是<code>for range</code>的方式。使用<code>for range</code>遍历通道，当通道被关闭的时候就会退出<code>for range</code>。</p> <h3 id="单向通道"><a href="#单向通道" class="header-anchor">#</a> 单向通道</h3> <p>有的时候我们会将通道作为参数在多个任务函数间传递，很多时候我们在不同的任务函数中使用通道都会对其进行限制，比如限制通道在函数中只能发送或只能接收。</p> <p>Go语言中提供了<strong>单向通道</strong>来处理这种情况。例如，我们把上面的例子改造如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">counter</span><span class="token punctuation">(</span>out <span class="token keyword">chan</span><span class="token operator">&lt;-</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		out <span class="token operator">&lt;-</span> i
	<span class="token punctuation">}</span>
	<span class="token function">close</span><span class="token punctuation">(</span>out<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">squarer</span><span class="token punctuation">(</span>out <span class="token keyword">chan</span><span class="token operator">&lt;-</span> <span class="token builtin">int</span><span class="token punctuation">,</span> in <span class="token operator">&lt;-</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> in <span class="token punctuation">{</span>
		out <span class="token operator">&lt;-</span> i <span class="token operator">*</span> i
	<span class="token punctuation">}</span>
	<span class="token function">close</span><span class="token punctuation">(</span>out<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">printer</span><span class="token punctuation">(</span>in <span class="token operator">&lt;-</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> in <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	ch1 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>
	ch2 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">counter</span><span class="token punctuation">(</span>ch1<span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">squarer</span><span class="token punctuation">(</span>ch2<span class="token punctuation">,</span> ch1<span class="token punctuation">)</span>
	<span class="token function">printer</span><span class="token punctuation">(</span>ch2<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div><p>其中，</p> <ul><li><code>chan&lt;- int</code>是一个只写单向通道（只能对其写入int类型值），可以对其执行发送操作但是不能执行接收操作；</li> <li><code>&lt;-chan int</code>是一个只读单向通道（只能从其读取int类型值），可以对其执行接收操作但是不能执行发送操作。</li></ul> <p>在函数传参及任何赋值操作中可以将双向通道转换为单向通道，但反过来是不可以的。</p> <h3 id="通道总结"><a href="#通道总结" class="header-anchor">#</a> 通道总结</h3> <p><code>channel</code>常见的异常总结，如下图：<img src="https://www.liwenzhou.com/images/Go/concurrence/channel01.png" alt="channel异常总结"></p> <p>关闭已经关闭的<code>channel</code>也会引发<code>panic</code>。</p> <h2 id="worker-pool-goroutine池"><a href="#worker-pool-goroutine池" class="header-anchor">#</a> worker pool（goroutine池）</h2> <p>在工作中我们通常会使用可以指定启动的goroutine数量–<code>worker pool</code>模式，控制<code>goroutine</code>的数量，防止<code>goroutine</code>泄漏和暴涨。</p> <p>一个简易的<code>work pool</code>示例代码如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">worker</span><span class="token punctuation">(</span>id <span class="token builtin">int</span><span class="token punctuation">,</span> jobs <span class="token operator">&lt;-</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> results <span class="token keyword">chan</span><span class="token operator">&lt;-</span> <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> j <span class="token operator">:=</span> <span class="token keyword">range</span> jobs <span class="token punctuation">{</span>
		fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;worker:%d start job:%d\n&quot;</span><span class="token punctuation">,</span> id<span class="token punctuation">,</span> j<span class="token punctuation">)</span>
		time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span>time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span>
		fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;worker:%d end job:%d\n&quot;</span><span class="token punctuation">,</span> id<span class="token punctuation">,</span> j<span class="token punctuation">)</span>
		results <span class="token operator">&lt;-</span> j <span class="token operator">*</span> <span class="token number">2</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>


<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	jobs <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span>
	results <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span>
	<span class="token comment">// 开启3个goroutine</span>
	<span class="token keyword">for</span> w <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> w <span class="token operator">&lt;=</span> <span class="token number">3</span><span class="token punctuation">;</span> w<span class="token operator">++</span> <span class="token punctuation">{</span>
		<span class="token keyword">go</span> <span class="token function">worker</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> jobs<span class="token punctuation">,</span> results<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	<span class="token comment">// 5个任务</span>
	<span class="token keyword">for</span> j <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> <span class="token number">5</span><span class="token punctuation">;</span> j<span class="token operator">++</span> <span class="token punctuation">{</span>
		jobs <span class="token operator">&lt;-</span> j
	<span class="token punctuation">}</span>
	<span class="token function">close</span><span class="token punctuation">(</span>jobs<span class="token punctuation">)</span>
	<span class="token comment">// 输出结果</span>
	<span class="token keyword">for</span> a <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">;</span> a <span class="token operator">&lt;=</span> <span class="token number">5</span><span class="token punctuation">;</span> a<span class="token operator">++</span> <span class="token punctuation">{</span>
		<span class="token operator">&lt;-</span>results
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br></div></div><h2 id="select多路复用"><a href="#select多路复用" class="header-anchor">#</a> select多路复用</h2> <p>在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时，如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">for</span><span class="token punctuation">{</span>    <span class="token comment">// 尝试从ch1接收值    data, ok := &lt;-ch1    // 尝试从ch2接收值    data, ok := &lt;-ch2    …}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>这种方式虽然可以实现从多个通道接收值的需求，但是运行性能会差很多。为了应对这种场景，Go内置了<code>select</code>关键字，可以同时响应多个通道的操作。</p> <p><code>select</code>的使用类似于switch语句，它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信（接收或发送）过程。<code>select</code>会一直等待，直到某个<code>case</code>的通信操作完成时，就会执行<code>case</code>分支对应的语句。具体格式如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">select</span><span class="token punctuation">{</span>    <span class="token keyword">case</span> <span class="token operator">&lt;-</span>ch1<span class="token punctuation">:</span>        <span class="token operator">...</span>    <span class="token keyword">case</span> data <span class="token operator">:=</span> <span class="token operator">&lt;-</span>ch2<span class="token punctuation">:</span>        <span class="token operator">...</span>    <span class="token keyword">case</span> ch3<span class="token operator">&lt;-</span>data<span class="token punctuation">:</span>        <span class="token operator">...</span>    <span class="token keyword">default</span><span class="token punctuation">:</span>        默认操作<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>举个小例子来演示下<code>select</code>的使用：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>	ch <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">chan</span> <span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>		<span class="token keyword">select</span> <span class="token punctuation">{</span>		<span class="token keyword">case</span> x <span class="token operator">:=</span> <span class="token operator">&lt;-</span>ch<span class="token punctuation">:</span>			fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span>		<span class="token keyword">case</span> ch <span class="token operator">&lt;-</span> i<span class="token punctuation">:</span>		<span class="token punctuation">}</span>	<span class="token punctuation">}</span><span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>使用<code>select</code>语句能提高代码的可读性。</p> <ul><li>可处理一个或多个channel的发送/接收操作。</li> <li>如果多个<code>case</code>同时满足，<code>select</code>会随机选择一个。</li> <li>对于没有<code>case</code>的<code>select{}</code>会一直等待，可用于阻塞main函数。</li></ul> <h2 id="并发安全和锁"><a href="#并发安全和锁" class="header-anchor">#</a> 并发安全和锁</h2> <p>有时候在Go代码中可能会存在多个<code>goroutine</code>同时操作一个资源（临界区），这种情况会发生<code>竞态问题</code>（数据竞态）。类比现实生活中的例子有十字路口被各个方向的的汽车竞争；还有火车上的卫生间被车厢里的人竞争。</p> <p>举个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> x <span class="token builtin">int64</span>
<span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup

<span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5000</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		x <span class="token operator">=</span> x <span class="token operator">+</span> <span class="token number">1</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><p>上面的代码中我们开启了两个<code>goroutine</code>去累加变量x的值，这两个<code>goroutine</code>在访问和修改<code>x</code>变量的时候就会存在数据竞争，导致最后的结果与期待的不符。</p> <h3 id="互斥锁"><a href="#互斥锁" class="header-anchor">#</a> 互斥锁</h3> <p>互斥锁是一种常用的控制共享资源访问的方法，它能够保证同时只有一个<code>goroutine</code>可以访问共享资源。Go语言中使用<code>sync</code>包的<code>Mutex</code>类型来实现互斥锁。 使用互斥锁来修复上面代码的问题：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> x <span class="token builtin">int64</span>
<span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup
<span class="token keyword">var</span> lock sync<span class="token punctuation">.</span>Mutex

<span class="token keyword">func</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5000</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		lock<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 加锁</span>
		x <span class="token operator">=</span> x <span class="token operator">+</span> <span class="token number">1</span>
		lock<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 解锁</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br></div></div><p>使用互斥锁能够保证同一时间有且只有一个<code>goroutine</code>进入临界区，其他的<code>goroutine</code>则在等待锁；当互斥锁释放后，等待的<code>goroutine</code>才可以获取锁进入临界区，多个<code>goroutine</code>同时等待一个锁时，唤醒的策略是随机的。</p> <h3 id="读写互斥锁"><a href="#读写互斥锁" class="header-anchor">#</a> 读写互斥锁</h3> <p>互斥锁是完全互斥的，但是有很多实际的场景下是读多写少的，当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的，这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用<code>sync</code>包中的<code>RWMutex</code>类型。</p> <p>读写锁分为两种：读锁和写锁。当一个goroutine获取读锁之后，其他的<code>goroutine</code>如果是获取读锁会继续获得锁，如果是获取写锁就会等待；当一个<code>goroutine</code>获取写锁之后，其他的<code>goroutine</code>无论是获取读锁还是写锁都会等待。</p> <p>读写锁示例：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> <span class="token punctuation">(</span>	x      <span class="token builtin">int64</span>	wg     sync<span class="token punctuation">.</span>WaitGroup	lock   sync<span class="token punctuation">.</span>Mutex	rwlock sync<span class="token punctuation">.</span>RWMutex<span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>	<span class="token comment">// lock.Lock()   // 加互斥锁	rwlock.Lock() // 加写锁	x = x + 1	time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒	rwlock.Unlock()                   // 解写锁	// lock.Unlock()                     // 解互斥锁	wg.Done()}func read() {	// lock.Lock()                  // 加互斥锁	rwlock.RLock()               // 加读锁	time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒	rwlock.RUnlock()             // 解读锁	// lock.Unlock()                // 解互斥锁	wg.Done()}func main() {	start := time.Now()	for i := 0; i &lt; 10; i++ {		wg.Add(1)		go write()	}	for i := 0; i &lt; 1000; i++ {		wg.Add(1)		go read()	}	wg.Wait()	end := time.Now()	fmt.Println(end.Sub(start))}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>需要注意的是读写锁非常适合读多写少的场景，如果读和写的操作差别不大，读写锁的优势就发挥不出来。</p> <h3 id="sync-waitgroup"><a href="#sync-waitgroup" class="header-anchor">#</a> sync.WaitGroup</h3> <p>在代码中生硬的使用<code>time.Sleep</code>肯定是不合适的，Go语言中可以使用<code>sync.WaitGroup</code>来实现并发任务的同步。 <code>sync.WaitGroup</code>有以下几个方法：</p> <table><thead><tr><th style="text-align:center;">方法名</th> <th style="text-align:center;">功能</th></tr></thead> <tbody><tr><td style="text-align:center;">(wg * WaitGroup) Add(delta int)</td> <td style="text-align:center;">计数器+delta</td></tr> <tr><td style="text-align:center;">(wg *WaitGroup) Done()</td> <td style="text-align:center;">计数器-1</td></tr> <tr><td style="text-align:center;">(wg *WaitGroup) Wait()</td> <td style="text-align:center;">阻塞直到计数器变为0</td></tr></tbody></table> <p><code>sync.WaitGroup</code>内部维护着一个计数器，计数器的值可以增加和减少。例如当我们启动了N 个并发任务时，就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完，当计数器值为0时，表示所有并发任务已经完成。</p> <p>我们利用<code>sync.WaitGroup</code>将上面的代码优化一下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">defer</span> wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;Hello Goroutine!&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
	<span class="token keyword">go</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 启动另外一个goroutine去执行hello函数</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">&quot;main goroutine done!&quot;</span><span class="token punctuation">)</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>需要注意<code>sync.WaitGroup</code>是一个结构体，传递的时候要传递指针。</p> <h3 id="sync-once"><a href="#sync-once" class="header-anchor">#</a> sync.Once</h3> <p>说在前面的话：这是一个进阶知识点。</p> <p>在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次，例如只加载一次配置文件、只关闭一次通道等。</p> <p>Go语言中的<code>sync</code>包中提供了一个针对只执行一次场景的解决方案–<code>sync.Once</code>。</p> <p><code>sync.Once</code>只有一个<code>Do</code>方法，其签名如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token punctuation">(</span>o <span class="token operator">*</span>Once<span class="token punctuation">)</span> <span class="token function">Do</span><span class="token punctuation">(</span>f <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p><em>备注：如果要执行的函数<code>f</code>需要传递参数就需要搭配闭包来使用。</em></p> <h4 id="加载配置文件示例"><a href="#加载配置文件示例" class="header-anchor">#</a> 加载配置文件示例</h4> <p>延迟一个开销很大的初始化操作到真正用到它的时候再执行是一个很好的实践。因为预先初始化一个变量（比如在init函数中完成初始化）会增加程序的启动耗时，而且有可能实际执行过程中这个变量没有用上，那么这个初始化操作就不是必须要做的。我们来看一个例子：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> icons <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span>image<span class="token punctuation">.</span>Image

<span class="token keyword">func</span> <span class="token function">loadIcons</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	icons <span class="token operator">=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span>image<span class="token punctuation">.</span>Image<span class="token punctuation">{</span>
		<span class="token string">&quot;left&quot;</span><span class="token punctuation">:</span>  <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;left.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;up&quot;</span><span class="token punctuation">:</span>    <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;up.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;right&quot;</span><span class="token punctuation">:</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;right.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;down&quot;</span><span class="token punctuation">:</span>  <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;down.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// Icon 被多个goroutine调用时不是并发安全的</span>
<span class="token keyword">func</span> <span class="token function">Icon</span><span class="token punctuation">(</span>name <span class="token builtin">string</span><span class="token punctuation">)</span> image<span class="token punctuation">.</span>Image <span class="token punctuation">{</span>
	<span class="token keyword">if</span> icons <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span>
		<span class="token function">loadIcons</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	<span class="token keyword">return</span> icons<span class="token punctuation">[</span>name<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p>多个<code>goroutine</code>并发调用Icon函数时不是并发安全的，现代的编译器和CPU可能会在保证每个<code>goroutine</code>都满足串行一致的基础上自由地重排访问内存的顺序。loadIcons函数可能会被重排为以下结果：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">func</span> <span class="token function">loadIcons</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	icons <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span>image<span class="token punctuation">.</span>Image<span class="token punctuation">)</span>
	icons<span class="token punctuation">[</span><span class="token string">&quot;left&quot;</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;left.png&quot;</span><span class="token punctuation">)</span>
	icons<span class="token punctuation">[</span><span class="token string">&quot;up&quot;</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;up.png&quot;</span><span class="token punctuation">)</span>
	icons<span class="token punctuation">[</span><span class="token string">&quot;right&quot;</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;right.png&quot;</span><span class="token punctuation">)</span>
	icons<span class="token punctuation">[</span><span class="token string">&quot;down&quot;</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;down.png&quot;</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>在这种情况下就会出现即使判断了<code>icons</code>不是nil也不意味着变量初始化完成了。考虑到这种情况，我们能想到的办法就是添加互斥锁，保证初始化<code>icons</code>的时候不会被其他的<code>goroutine</code>操作，但是这样做又会引发性能问题。</p> <p>使用<code>sync.Once</code>改造的示例代码如下：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> icons <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span>image<span class="token punctuation">.</span>Image

<span class="token keyword">var</span> loadIconsOnce sync<span class="token punctuation">.</span>Once

<span class="token keyword">func</span> <span class="token function">loadIcons</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	icons <span class="token operator">=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span>image<span class="token punctuation">.</span>Image<span class="token punctuation">{</span>
		<span class="token string">&quot;left&quot;</span><span class="token punctuation">:</span>  <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;left.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;up&quot;</span><span class="token punctuation">:</span>    <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;up.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;right&quot;</span><span class="token punctuation">:</span> <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;right.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token string">&quot;down&quot;</span><span class="token punctuation">:</span>  <span class="token function">loadIcon</span><span class="token punctuation">(</span><span class="token string">&quot;down.png&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// Icon 是并发安全的</span>
<span class="token keyword">func</span> <span class="token function">Icon</span><span class="token punctuation">(</span>name <span class="token builtin">string</span><span class="token punctuation">)</span> image<span class="token punctuation">.</span>Image <span class="token punctuation">{</span>
	loadIconsOnce<span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span>loadIcons<span class="token punctuation">)</span>
	<span class="token keyword">return</span> icons<span class="token punctuation">[</span>name<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h4 id="并发安全的单例模式"><a href="#并发安全的单例模式" class="header-anchor">#</a> 并发安全的单例模式</h4> <p>下面是借助<code>sync.Once</code>实现的并发安全的单例模式：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">package</span> singleton

<span class="token keyword">import</span> <span class="token punctuation">(</span>
    <span class="token string">&quot;sync&quot;</span>
<span class="token punctuation">)</span>

<span class="token keyword">type</span> singleton <span class="token keyword">struct</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword">var</span> instance <span class="token operator">*</span>singleton
<span class="token keyword">var</span> once sync<span class="token punctuation">.</span>Once

<span class="token keyword">func</span> <span class="token function">GetInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span>singleton <span class="token punctuation">{</span>
    once<span class="token punctuation">.</span><span class="token function">Do</span><span class="token punctuation">(</span><span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        instance <span class="token operator">=</span> <span class="token operator">&amp;</span>singleton<span class="token punctuation">{</span><span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> instance
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><p><code>sync.Once</code>其实内部包含一个互斥锁和一个布尔值，互斥锁保证布尔值和数据的安全，而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。</p> <h3 id="sync-map"><a href="#sync-map" class="header-anchor">#</a> sync.Map</h3> <p>Go语言中内置的map不是并发安全的。请看下面的示例：</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> m <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">get</span><span class="token punctuation">(</span>key <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token builtin">int</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> m<span class="token punctuation">[</span>key<span class="token punctuation">]</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">set</span><span class="token punctuation">(</span>key <span class="token builtin">string</span><span class="token punctuation">,</span> value <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	m<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> value
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	wg <span class="token operator">:=</span> sync<span class="token punctuation">.</span>WaitGroup<span class="token punctuation">{</span><span class="token punctuation">}</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">20</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
		<span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span>n <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			key <span class="token operator">:=</span> strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span>
			<span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> n<span class="token punctuation">)</span>
			fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;k=:%v,v:=%v\n&quot;</span><span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span>
			wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token punctuation">}</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><p>上面的代码开启少量几个<code>goroutine</code>的时候可能没什么问题，当并发多了之后执行上面的代码就会报<code>fatal error: concurrent map writes</code>错误。</p> <p>像这种场景下就需要为map加锁来保证并发的安全性了，Go语言的<code>sync</code>包中提供了一个开箱即用的并发安全版map–<code>sync.Map</code>。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时<code>sync.Map</code>内置了诸如<code>Store</code>、<code>Load</code>、<code>LoadOrStore</code>、<code>Delete</code>、<code>Range</code>等操作方法。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">var</span> m <span class="token operator">=</span> sync<span class="token punctuation">.</span>Map<span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	wg <span class="token operator">:=</span> sync<span class="token punctuation">.</span>WaitGroup<span class="token punctuation">{</span><span class="token punctuation">}</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">20</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
		<span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span>n <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			key <span class="token operator">:=</span> strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span>
			m<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> n<span class="token punctuation">)</span>
			value<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> m<span class="token punctuation">.</span><span class="token function">Load</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span>
			fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">&quot;k=:%v,v:=%v\n&quot;</span><span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span>
			wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token punctuation">}</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><h2 id="原子操作"><a href="#原子操作" class="header-anchor">#</a> 原子操作</h2> <p>在上面的代码中的我们通过锁操作来实现同步。而锁机制的底层是基于原子操作的，其一般直接通过CPU指令实现。Go语言中原子操作由内置的标准库<code>sync/atomic</code>提供。</p> <h3 id="atomic包"><a href="#atomic包" class="header-anchor">#</a> atomic包</h3> <table><thead><tr><th style="text-align:center;">方法</th> <th style="text-align:center;">解释</th></tr></thead> <tbody><tr><td style="text-align:center;">func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64) func LoadUint32(addr *uint32) (val uint32) func LoadUint64(addr *uint64) (val uint64) func LoadUintptr(addr *uintptr) (val uintptr) func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)</td> <td style="text-align:center;">读取操作</td></tr> <tr><td style="text-align:center;">func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)</td> <td style="text-align:center;">写入操作</td></tr> <tr><td style="text-align:center;">func AddInt32(addr *int32, delta int32) (new int32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)</td> <td style="text-align:center;">修改操作</td></tr> <tr><td style="text-align:center;">func SwapInt32(addr *int32, new int32) (old int32) func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)</td> <td style="text-align:center;">交换操作</td></tr> <tr><td style="text-align:center;">func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)</td> <td style="text-align:center;">比较并交换操作</td></tr></tbody></table> <h3 id="示例"><a href="#示例" class="header-anchor">#</a> 示例</h3> <p>我们填写一个示例来比较下互斥锁和原子操作的性能。</p> <div class="language-go line-numbers-mode"><pre class="language-go"><code><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">&quot;fmt&quot;</span>
	<span class="token string">&quot;sync&quot;</span>
	<span class="token string">&quot;sync/atomic&quot;</span>
	<span class="token string">&quot;time&quot;</span>
<span class="token punctuation">)</span>

<span class="token keyword">type</span> Counter <span class="token keyword">interface</span> <span class="token punctuation">{</span>
	<span class="token function">Inc</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token function">Load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">int64</span>
<span class="token punctuation">}</span>

<span class="token comment">// 普通版</span>
<span class="token keyword">type</span> CommonCounter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	counter <span class="token builtin">int64</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>c CommonCounter<span class="token punctuation">)</span> <span class="token function">Inc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	c<span class="token punctuation">.</span>counter<span class="token operator">++</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>c CommonCounter<span class="token punctuation">)</span> <span class="token function">Load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">int64</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> c<span class="token punctuation">.</span>counter
<span class="token punctuation">}</span>

<span class="token comment">// 互斥锁版</span>
<span class="token keyword">type</span> MutexCounter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	counter <span class="token builtin">int64</span>
	lock    sync<span class="token punctuation">.</span>Mutex
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>MutexCounter<span class="token punctuation">)</span> <span class="token function">Inc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	m<span class="token punctuation">.</span>lock<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">defer</span> m<span class="token punctuation">.</span>lock<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	m<span class="token punctuation">.</span>counter<span class="token operator">++</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>m <span class="token operator">*</span>MutexCounter<span class="token punctuation">)</span> <span class="token function">Load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">int64</span> <span class="token punctuation">{</span>
	m<span class="token punctuation">.</span>lock<span class="token punctuation">.</span><span class="token function">Lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">defer</span> m<span class="token punctuation">.</span>lock<span class="token punctuation">.</span><span class="token function">Unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">return</span> m<span class="token punctuation">.</span>counter
<span class="token punctuation">}</span>

<span class="token comment">// 原子操作版</span>
<span class="token keyword">type</span> AtomicCounter <span class="token keyword">struct</span> <span class="token punctuation">{</span>
	counter <span class="token builtin">int64</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>AtomicCounter<span class="token punctuation">)</span> <span class="token function">Inc</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	atomic<span class="token punctuation">.</span><span class="token function">AddInt64</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>a<span class="token punctuation">.</span>counter<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token punctuation">(</span>a <span class="token operator">*</span>AtomicCounter<span class="token punctuation">)</span> <span class="token function">Load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token builtin">int64</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> atomic<span class="token punctuation">.</span><span class="token function">LoadInt64</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>a<span class="token punctuation">.</span>counter<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">test</span><span class="token punctuation">(</span>c Counter<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">var</span> wg sync<span class="token punctuation">.</span>WaitGroup
	start <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">1000</span><span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">{</span>
		wg<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
		<span class="token keyword">go</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			c<span class="token punctuation">.</span><span class="token function">Inc</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			wg<span class="token punctuation">.</span><span class="token function">Done</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
		<span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	<span class="token punctuation">}</span>
	wg<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	end <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">Load</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> end<span class="token punctuation">.</span><span class="token function">Sub</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	c1 <span class="token operator">:=</span> CommonCounter<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 非并发安全</span>
	<span class="token function">test</span><span class="token punctuation">(</span>c1<span class="token punctuation">)</span>
	c2 <span class="token operator">:=</span> MutexCounter<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 使用互斥锁实现并发安全</span>
	<span class="token function">test</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>c2<span class="token punctuation">)</span>
	c3 <span class="token operator">:=</span> AtomicCounter<span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 并发安全且比互斥锁效率更高</span>
	<span class="token function">test</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>c3<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br></div></div><p><code>atomic</code>包提供了底层的原子级内存操作，对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用，使用通道或者sync包的函数/类型实现同步更好。</p></div></section> <footer class="page-edit"><!----> <div class="last-updated"><span class="prefix">2021-9-2: </span> <span class="time">9/3/2021, 6:20:18 PM</span></div></footer> <div class="page-nav"><p class="inner"><span class="prev"><a href="/blogs/GO/go/13_go_package.html" class="prev">
            Go语言基础之包(package)
          </a></span> <span class="next"><a href="/blogs/GO/go/15_go_socket_udp_tcp.html">
            Go语言基础之网络编程
          </a></span></p></div> <div class="comments-wrapper"><!----></div> <ul class="side-bar sub-sidebar-wrapper" style="width:12rem;" data-v-70334359><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#并发与并行" class="sidebar-link reco-side-并发与并行" data-v-70334359>并发与并行</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#goroutine" class="sidebar-link reco-side-goroutine" data-v-70334359>goroutine</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#使用goroutine" class="sidebar-link reco-side-使用goroutine" data-v-70334359>使用goroutine</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#启动单个goroutine" class="sidebar-link reco-side-启动单个goroutine" data-v-70334359>启动单个goroutine</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#启动多个goroutine" class="sidebar-link reco-side-启动多个goroutine" data-v-70334359>启动多个goroutine</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#goroutine与线程" class="sidebar-link reco-side-goroutine与线程" data-v-70334359>goroutine与线程</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#可增长的栈" class="sidebar-link reco-side-可增长的栈" data-v-70334359>可增长的栈</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#goroutine调度" class="sidebar-link reco-side-goroutine调度" data-v-70334359>goroutine调度</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#gomaxprocs" class="sidebar-link reco-side-gomaxprocs" data-v-70334359>GOMAXPROCS</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#channel" class="sidebar-link reco-side-channel" data-v-70334359>channel</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#channel类型" class="sidebar-link reco-side-channel类型" data-v-70334359>channel类型</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#创建channel" class="sidebar-link reco-side-创建channel" data-v-70334359>创建channel</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#channel操作" class="sidebar-link reco-side-channel操作" data-v-70334359>channel操作</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#无缓冲的通道" class="sidebar-link reco-side-无缓冲的通道" data-v-70334359>无缓冲的通道</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#有缓冲的通道" class="sidebar-link reco-side-有缓冲的通道" data-v-70334359>有缓冲的通道</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#for-range从通道循环取值" class="sidebar-link reco-side-for-range从通道循环取值" data-v-70334359>for range从通道循环取值</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#单向通道" class="sidebar-link reco-side-单向通道" data-v-70334359>单向通道</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#通道总结" class="sidebar-link reco-side-通道总结" data-v-70334359>通道总结</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#worker-pool-goroutine池" class="sidebar-link reco-side-worker-pool-goroutine池" data-v-70334359>worker pool（goroutine池）</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#select多路复用" class="sidebar-link reco-side-select多路复用" data-v-70334359>select多路复用</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#并发安全和锁" class="sidebar-link reco-side-并发安全和锁" data-v-70334359>并发安全和锁</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#互斥锁" class="sidebar-link reco-side-互斥锁" data-v-70334359>互斥锁</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#读写互斥锁" class="sidebar-link reco-side-读写互斥锁" data-v-70334359>读写互斥锁</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#sync-waitgroup" class="sidebar-link reco-side-sync-waitgroup" data-v-70334359>sync.WaitGroup</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#sync-once" class="sidebar-link reco-side-sync-once" data-v-70334359>sync.Once</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#sync-map" class="sidebar-link reco-side-sync-map" data-v-70334359>sync.Map</a></li><li class="level-2" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#原子操作" class="sidebar-link reco-side-原子操作" data-v-70334359>原子操作</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#atomic包" class="sidebar-link reco-side-atomic包" data-v-70334359>atomic包</a></li><li class="level-3" data-v-70334359><a href="/blogs/GO/go/14_go_channel_lock.html#示例" class="sidebar-link reco-side-示例" data-v-70334359>示例</a></li></ul></main> <!----></div></div></div></div><div class="global-ui"><div class="back-to-ceiling" style="right:1rem;bottom:6rem;width:2.5rem;height:2.5rem;border-radius:.25rem;line-height:2.5rem;display:none;" data-v-c6073ba8 data-v-c6073ba8><svg t="1574745035067" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5404" class="icon" data-v-c6073ba8><path d="M526.60727968 10.90185116a27.675 27.675 0 0 0-29.21455937 0c-131.36607665 82.28402758-218.69155461 228.01873535-218.69155402 394.07834331a462.20625001 462.20625001 0 0 0 5.36959153 69.94390903c1.00431239 6.55289093-0.34802892 13.13561351-3.76865779 18.80351572-32.63518765 54.11355614-51.75690182 118.55860487-51.7569018 187.94566865a371.06718723 371.06718723 0 0 0 11.50484808 91.98906777c6.53300375 25.50556257 41.68394495 28.14064038 52.69160883 4.22606766 17.37162448-37.73630017 42.14135425-72.50938081 72.80769204-103.21549295 2.18761121 3.04276886 4.15646224 6.24463696 6.40373557 9.22774369a1871.4375 1871.4375 0 0 0 140.04691725 5.34970492 1866.36093723 1866.36093723 0 0 0 140.04691723-5.34970492c2.24727335-2.98310674 4.21612437-6.18497483 6.3937923-9.2178004 30.66633723 30.70611158 55.4360664 65.4791928 72.80769147 103.21549355 11.00766384 23.91457269 46.15860503 21.27949489 52.69160879-4.22606768a371.15156223 371.15156223 0 0 0 11.514792-91.99901164c0-69.36717486-19.13165746-133.82216804-51.75690182-187.92578088-3.42062944-5.66790279-4.76302748-12.26056868-3.76865837-18.80351632a462.20625001 462.20625001 0 0 0 5.36959269-69.943909c-0.00994388-166.08943902-87.32547796-311.81420293-218.6915546-394.09823051zM605.93803103 357.87693858a93.93749974 93.93749974 0 1 1-187.89594924 6.1e-7 93.93749974 93.93749974 0 0 1 187.89594924-6.1e-7z" p-id="5405" data-v-c6073ba8></path><path d="M429.50777625 765.63860547C429.50777625 803.39355007 466.44236686 1000.39046097 512.00932183 1000.39046097c45.56695499 0 82.4922232-197.00623328 82.5015456-234.7518555 0-37.75494459-36.9345906-68.35043303-82.4922232-68.34111062-45.57627738-0.00932239-82.52019037 30.59548842-82.51086798 68.34111062z" p-id="5406" data-v-c6073ba8></path></svg></div><!----><canvas id="vuepress-canvas-cursor"></canvas></div></div>
    <script src="/assets/js/app.7029ddab.js" defer></script><script src="/assets/js/3.ebaa3085.js" defer></script><script src="/assets/js/1.8ce67e8c.js" defer></script><script src="/assets/js/15.74a9f029.js" defer></script>
  </body>
</html>
